package com.eian.boot.common.core.aspect;

import com.eian.boot.common.core.adapter.TtlMDCAdapter;
import com.eian.boot.common.core.annotation.Trace;
import com.eian.boot.common.core.constants.SystemConstants;
import com.eian.boot.common.core.utils.SpElUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author alex.meng
 * @createTime 2025-09-12 16:25
 * @description 链路注解切面，支持从方法参数中通过 SpEL 提取 traceId，
 * 即使提取到 traceId，也视为新链路（isNewTrace = true），方法结束后清理 MDC。
 * 使用 SpElUtils 统一解析 SpEL 表达式。
 */
@Slf4j
@Aspect
@Component
public class TraceAspect {

    @Around("@annotation(com.eian.boot.common.core.annotation.Trace)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Trace traceAnnotation = method.getAnnotation(Trace.class);

        String existingTraceId = TtlMDCAdapter.get(SystemConstants.TRACE_ID_MDC_KEY);
        boolean isNewTrace = StringUtils.isBlank(existingTraceId);

        if (isNewTrace) {
            String extractedTraceId = null;
            if (StringUtils.isNotBlank(traceAnnotation.spel())) {
                try {
                    extractedTraceId = SpElUtils.parseSpEl(method, joinPoint.getArgs(), traceAnnotation.spel());
                } catch (Exception e) {
                    log.warn("SpEL 表达式 [{}] 未能解析出 traceId，将使用自动生成的 traceId", traceAnnotation.spel(), e);
                }
            }
            // 最终使用的 traceId：优先 SpEL 提取 → 自动生成
            String traceIdToUse = StringUtils.isNotBlank(extractedTraceId) ? extractedTraceId : TtlMDCAdapter.generateTraceId();
            TtlMDCAdapter.put(SystemConstants.TRACE_ID_MDC_KEY, traceIdToUse);
        }

        try {
            return joinPoint.proceed();
        } finally {
            if (isNewTrace) {
                TtlMDCAdapter.clear();
            }
        }
    }
}